/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package net.sourceforge.slcwsn.network;

import com.google.code.xbeejavaapi.api.DiscoveredNode;
import com.google.code.xbeejavaapi.api.Filter;
import com.google.code.xbeejavaapi.api.LocalXBee;
import com.google.code.xbeejavaapi.api.LocalXBee.XBeeInputStream;
import com.google.code.xbeejavaapi.api.XBee;
import com.google.code.xbeejavaapi.api.XBeeAddress;
import com.google.code.xbeejavaapi.api.exception.XBeeOperationFailedException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sourceforge.slcwsn.event.DefaultEventVisitor;
import net.sourceforge.slcwsn.event.Event;
import net.sourceforge.slcwsn.event.EventFactory;
import net.sourceforge.slcwsn.event.LostSensorMessagesEvent;
import net.sourceforge.slcwsn.event.PIRDetectionEvent;
import net.sourceforge.slcwsn.event.ParsingException;
import net.sourceforge.slcwsn.event.SensorAliveEvent;
import net.sourceforge.slcwsn.event.SensorDiscoveredEvent;
import net.sourceforge.slcwsn.event.SuperNodeDiscoveredEvent;
import net.sourceforge.slcwsn.event.TransientEvent;
import net.sourceforge.slcwsn.sensors.Sensor;
import net.sourceforge.slcwsn.sensors.SensorCreationException;
import net.sourceforge.slcwsn.supernodes.SuperNode;
import org.apache.log4j.Logger;

/**
 *
 * @author David Miguel Antunes <davidmiguel [ at ] antunes.net>
 */
public class Network implements LocalXBee.DiscoveredNodeListener {

    private long eventCounter = 0;

    public class XBeeListener {

        private XBee xbee;
        private XBeeInputStream in;

        public XBeeListener(XBeeInputStream in, XBee xbee) {
            this.xbee = xbee;
            this.in = in;
        }

        public void listen() {
            new Thread() {

                @Override
                public void run() {
                    EventFactory factory = new EventFactory(Network.this);
                    while (true) {
                        try {
                            String line = "";
                            char c = (char) in.read();
                            while (c != '\n' && c != '\r') {
                                line += "" + c;
                                c = (char) in.read();
                            }
                            factory.parse(line, System.currentTimeMillis(), xbee);
                        } catch (ParsingException ex) {
                            logger.error(ex);
                        } catch (SensorCreationException ex) {
                            logger.error(ex);
                        } catch (IOException ex) {
                            logger.error(ex);
                        }
                    }
                }
            }.start();
        }
    }

    private class InternalStateUpdater extends DefaultEventVisitor implements NetworkObserver {

        public void newEvent(Event event) {
            events.put(event.getId(), event);
            event.accept(this);
        }

        public void visit(SensorDiscoveredEvent event) {
            sensors.put(event.getSensor().getId(), event.getSensor());
        }
    }
    private static final Logger logger = Logger.getLogger(Network.class);
    private Map<XBeeAddress, SuperNode> superNodes = new HashMap<XBeeAddress, SuperNode>();
    private Map<Long, Sensor> sensors = new HashMap<Long, Sensor>();
    private Map<Long, Event> events = new HashMap<Long, Event>();
    private List<NetworkObserver> observers = new ArrayList<NetworkObserver>();
    private LocalXBee xbee;
    private final Object superNodesDiscoveryLock = new Object();

    public Network(LocalXBee xbee) {
        this.xbee = xbee;
        observers.add(new InternalStateUpdater());
        xbee.addDiscoveredNodeListener(this);
    }

    public void start() throws XBeeOperationFailedException {
    }

    public Map<Long, Sensor> getSensors() {
        return sensors;
    }

    public void newEvent(Event event) {
        System.out.println("New event: " + event);
        for (NetworkObserver networkObserver : observers) {
            networkObserver.newEvent(event);
        }

        if (!(event instanceof TransientEvent)) {
            events.put(event.getId(), event);
        }
    }

    public long generateEventId() {
        return eventCounter++;
    }

    public Collection<NetworkObserver> getObservers() {
        return observers;
    }

    public void discoverSuperNodes() throws XBeeOperationFailedException {
        synchronized (superNodesDiscoveryLock) {
            logger.info("Discovering super nodes...");
            Set<DiscoveredNode> nodes = xbee.searchNodes();
            logger.info("Discovered " + nodes.size() + " nodes in the network.");
        }
    }

    public void nodeFound(DiscoveredNode node) {
        logger.info("Adding SuperNode " + node.getNodeIdentifier() + " to the network.");
        SuperNode superNode = superNodes.get(node.getAddress());
        if (superNode == null) {
            try {
                superNode = new SuperNode(node.getXbee());
                newEvent(new SuperNodeDiscoveredEvent(System.currentTimeMillis(), superNode, this));
                logger.info("Opening node " + node.getNodeIdentifier() + " (" + node.getAddress() + ").");
                XBeeListener listener = new XBeeListener(xbee.openInputStream(new Filter(true, node.getAddress(), false, 0, false, 0, false, 0, false, 0)), xbee.openRemoteXBee(node.getAddress()));
                listener.listen();
            } catch (XBeeOperationFailedException ex) {
                logger.error(ex);
            }
        }
    }
}
